package route;

import dao.GroupDAO;
import dao.RelationDAO;
import io.netty.channel.Channel;
import message.receive.MsgFromClientReceive;
import message.receive.MsgFromGroupReceive;
import util.Constants;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class RouterImpl implements Router
{
    private final ConcurrentMap<String, Object>    clientRouter         = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, GroupInfo> groupRouter = new ConcurrentHashMap<>();
    private       GroupDAO                         groupDAO;
    private       RelationDAO                      relationDAO;

    public RouterImpl(GroupDAO groupDAO, RelationDAO relationDAO)
    {
        this.groupDAO = groupDAO;
        this.relationDAO = relationDAO;
    }

    class GroupInfo
    {
        String                        groupName;
        ConcurrentMap<String, Object> members = new ConcurrentHashMap<>();

        public GroupInfo(String groupName)
        {
            this.groupName = groupName;
        }
    }

    private void addElementToMap(ConcurrentMap<String, Object> map, String clientId, Channel channel)
    {
        Object exist = map.get(clientId);
        while (true)
        {
            if (exist == null)
            {
                if ((exist = map.putIfAbsent(clientId, channel)) == null)
                {
                    break;
                }
            }
            else
            {
                Channel[] array = null;
                if (exist instanceof Channel)
                {
                    if (exist==channel)
                    {
                        break;
                    }
                    array = new Channel[2];
                    array[0] = (Channel) exist;
                    array[1] = channel;
                }
                else
                {
                    Channel[] oldArray = (Channel[]) exist;
                    for (Channel member : oldArray)
                    {
                        if (member==channel)
                        {
                            break;
                        }
                    }
                    array = new Channel[oldArray.length + 1];
                    System.arraycopy(oldArray, 0, array, 1, oldArray.length);
                    array[0] = channel;
                }
                if (map.replace(clientId, exist, array))
                {
                    break;
                }
                else
                {
                    exist = map.get(clientId);
                }
            }
        }
    }

    private void removeElement(ConcurrentMap<String, Object> map, String clientId, Channel channel)
    {
        Object exist = map.get(clientId);
        while (true)
        {
            if (exist == channel)
            {
                if (map.remove(clientId, channel))
                {
                    break;
                }
                else
                {
                    exist = map.get(clientId);
                }
            }
            else if (exist instanceof Channel[])
            {
                Channel[] array = (Channel[]) exist;
                if (array.length == 2)
                {
                    Channel newValue = array[0] == channel ? array[1] : array[0];
                    if (map.replace(clientId, exist, newValue))
                    {
                        break;
                    }
                }
                else
                {
                    ArrayList<Channel> newArrays = new ArrayList<>();
                    for (Channel each : array)
                    {
                        if (each != channel)
                        {
                            newArrays.add(each);
                        }
                    }
                    Channel[] channels = newArrays.toArray(new Channel[0]);
                    if (map.replace(clientId, exist, channels))
                    {
                        break;
                    }
                }
                exist = map.get(clientId);
            }
            else
            {
                throw new IllegalStateException();
            }
        }
    }

    @Override
    public void clientOnline(String clientId, Channel channel)
    {
        addElementToMap(clientRouter, clientId, channel);
        onlineToGroup(clientId, channel);
    }

    private void onlineToGroup(String clientId, Channel channel)
    {
        Collection<String> groups = relationDAO.findGroups(clientId);
        if (groups==null)
        {
            return;
        }
        for (String groupId : groups)
        {
            GroupInfo groupInfo = groupRouter.get(groupId);
            if (groupInfo != null)
            {
                addToGroup(groupId, clientId, channel);
            }
        }
    }

    @Override
    public void clientOffline(String clientId, Channel channel)
    {
        removeElement(clientRouter, clientId, channel);
        Collection<String> groups = relationDAO.findGroups(clientId);
        if (groups!=null && groups.isEmpty()==false)
        {
            for (String group : groups)
            {
                GroupInfo groupInfo = groupRouter.get(group);
                if (groupInfo!=null)
                {
                    removeElement(groupInfo.members, clientId, channel);
                }
            }
        }
    }

    @Override
    public void sendToClient(String clientId, String msg)
    {
        Object exist = clientRouter.get(clientId);
        if (exist == null)
        {
            return;
        }
        sendToChannel(msg, exist);
    }

    private void sendToChannel(String msg, Object dest)
    {
        if (dest instanceof Channel)
        {
            MsgFromClientReceive receive = new MsgFromClientReceive();
            Channel              channel = (Channel) dest;
            receive.setClientName(channel.attr(Constants.CLIENT_NAME).get());
            receive.setMsg(msg);
            channel.writeAndFlush(receive);
        }
        else
        {
            Channel[] channels = (Channel[]) dest;
            for (Channel each : channels)
            {
                try
                {
                    MsgFromClientReceive receive = new MsgFromClientReceive();
                    receive.setClientName(each.attr(Constants.CLIENT_NAME).get());
                    receive.setMsg(msg);
                    each.writeAndFlush(receive);
                }
                finally
                {
                    //数组形态意味着存在着失效链接的情况，在这里不必理会，只需要继续发送即可。
                }
            }
        }
    }

    @Override
    public void addToGroup(String groupId, String clientId, Channel channel)
    {
        GroupInfo groupInfo = initGroupIfNeed(groupId);
        addElementToMap(groupInfo.members, clientId, channel);
    }

    private GroupInfo initGroupIfNeed(String groupId)
    {
        GroupInfo groupInfo = groupRouter.get(groupId);
        if (groupInfo == null)
        {
            groupInfo = new GroupInfo(groupDAO.getGroupNameById(groupId));
            for (String memberId : relationDAO.findClients(groupId))
            {
                Object o = clientRouter.get(memberId);
                if (o != null)
                {
                    groupInfo.members.put(memberId, o);
                }
            }
            groupRouter.putIfAbsent(groupId, groupInfo);
        }
        return groupInfo;
    }

    @Override
    public void newGroup(String groupId, String groupName)
    {
        GroupInfo groupInfo = new GroupInfo(groupName);
        groupRouter.putIfAbsent(groupId, groupInfo);
    }

    @Override
    public void sendToGroup(String groupName, String srcClientId, String msg)
    {
        String groupId = groupDAO.find(groupName);
        if (groupId == null)
        {
            return;
        }
        GroupInfo groupInfo =initGroupIfNeed(groupId);
        Object src = groupInfo.members.get(srcClientId);
        String clientName;
        if (src instanceof Channel)
        {
            clientName = ((Channel) src).attr(Constants.CLIENT_NAME).get();
        }
        else
        {
            clientName = ((Channel[]) src)[0].attr(Constants.CLIENT_NAME).get();
        }
        MsgFromGroupReceive receive = new MsgFromGroupReceive();
        receive.setMsg(msg);
        receive.setClientName(clientName);
        receive.setGroupName(groupName);
        for (Map.Entry<String, Object> each : groupInfo.members.entrySet())
        {
            if (each.getKey().equals(srcClientId))
            {
                continue;
            }
            Object dest = each.getValue();
            try
            {
                if (dest instanceof Channel)
                {
                    ((Channel) dest).writeAndFlush(receive);
                }
                else
                {
                    for (Channel channel : ((Channel[]) dest))
                    {
                        channel.writeAndFlush(receive);
                    }
                }
            }
            catch (Exception e)
            {
                //忽略异常，不影响其他通道的消息发送
                e.printStackTrace();
            }
        }
    }
}
